package websocket

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"engine/PbMsgHelper"
	"engine/internal"
	"engine/log"

	"gitee.com/ameise84/e3dds"
	net "gitee.com/ameise84/e3net"
	"gitee.com/ameise84/e3pool/go_pool"
	"gitee.com/ameise84/e3pool/obj_pool"
	"gitee.com/ameise84/e3utils/str_conv"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
)

var (
	_instID       atomic.Uint64
	_gRawCtxPool  obj_pool.Pool[*rawContext]
	_readTimeout  time.Duration
	_writeTimeout time.Duration
)

func init() {
	_gRawCtxPool = obj_pool.NewPool[*rawContext](func() *rawContext {
		return &rawContext{
			id: str_conv.ToString(_instID.Add(1)),
		}
	})
}

func takeRawContext() *rawContext {
	return _gRawCtxPool.Get()
}

func freeRawContext(rc *rawContext) {
	go_pool.NewGoWorkerDo(nil, "free ws raw context", func(...any) {
		rc.wg.Wait() //等待消息返回
		rc.c = nil
		rc.actor = nil
		_gRawCtxPool.Put(rc)
	})
}

// 代理连接
type rawContext struct {
	id          string
	c           *websocket.Conn
	actor       e3dds.NetBehavior
	isConnected atomic.Bool
	wg          sync.WaitGroup

	recvCount uint64
}

func (ts *rawContext) E3LogMarshall() string {
	return fmt.Sprintf("ws raw context[%s]", ts.id)
}

func (ts *rawContext) OnPush(protoc string, msg []byte) {
	actor := ts.actor.GetActor()
	if netActor, ok := actor.(internal.NetActor); ok {
		body := _gMsgPackHand.RpcMsgToNetMsg(netActor, protoc, msg)
		ts.send(body)
	} else {
		log.GetLogger().Error("failed to get NetActor from actor").Println()
	}
}

func (ts *rawContext) OnResponse(rsp e3dds.Response) {
	actor := ts.actor.GetActor()
	if netActor, ok := actor.(internal.NetActor); ok {
		body := _gMsgPackHand.RpcMsgToNetMsg(netActor, rsp.GetProtocName(), rsp.GetBody())
		ts.wg.Done()
		ts.send(body)
	} else {
		log.GetLogger().Error("failed to get NetActor from actor").Println()
	}
}

func (ts *rawContext) onRecv(body []byte) error {
	actor := ts.actor.GetActor()
	if netActor, ok := actor.(internal.NetActor); ok {
		protoc, msgBody, err := _gMsgPackHand.NetMsgToRpcMsg(netActor, body)
		if err != nil {
			return err
		}
		ts.recvCount++
		msg := e3dds.TakeMessage()
		msg.SetID(ts.recvCount)
		msg.SetToActor(ts.actor.GetSource().ServiceID(), "", ts.actor.GetSource().ClerkID())
		msg.SetMsg(protoc, msgBody)
		msg.SetRspHandler(ts)
		err = ts.actor.OnRecvNetMsg(msg)
		if err != nil {
			msg.ResponseMsg(pbMsgHelper.ToRpcMsg(_gMsgPackHand.ResponseError(err)))
			e3dds.FreeMessage(msg)
		} else {
			ts.wg.Add(1)
		}
		return err
	} else {
		log.GetLogger().Error("failed to get NetActor from actor").Println()
		return errors.New("invalid actor type")
	}
}

func (ts *rawContext) Close() {
	if ts.isConnected.CompareAndSwap(true, false) {
		_ = ts.c.Close()
	}
}

func (ts *rawContext) onInit(c *websocket.Conn, actor e3dds.NetBehavior) {
	ts.c = c
	ts.actor = actor
	actor.BindNetContext(ts)
	ts.isConnected.Store(true)
}

func (ts *rawContext) onClosed() {
	ts.isConnected.Store(false)
	ts.actor.UnBindNetContext()
}

func (ts *rawContext) send(body []byte) {
	c := ts.c
	var err error
	if ts.isConnected.Load() {
		if _writeTimeout > 0 {
			err = c.SetWriteDeadline(time.Now().Add(_writeTimeout))
		}
		if err == nil {
			err = c.WriteMessage(websocket.BinaryMessage, body)
		}
	} else {
		err = net.ErrorConnClosed
	}
	if err != nil && !errors.Is(err, net.ErrorConnClosed) {
		log.GetLogger().Warn("send to client").Err(err).Object(ts).Println()
	}
}
